"""
World
Tracks where everything is. Also, tests distance and collisions.
"""
import logging

#import boat, island, fish, pirates, triggers, port
from point import Point, AxisAlignedBoundingBox


class CollisionError(Exception):
    """ Represents an attempt to move one object into another. """
    def __init__(self, collider, colliderLoc, collidee, collideeLoc):
        Exception.__init__(self)
        self.collider = collider
        self.colliderLoc = colliderLoc
        self.collidee = collidee
        self.collideeLoc = collideeLoc


class CoordinatePlane(object):
    """
    The absolute bare minimum needed for a usable coordinate plane. Stores
    items and locations, can get distance, can retrieve items in bulk, can move 
    items "in-place", and handle certain collisions.
    Used as a base class in islands, boats(?), etc.
    """
    def __init__(self, itemLocs={}, game=None):
        for item, coord in itemLocs:
            self.items[item] = coord
            self.locations[coord] = item
        self.game = game
    
    def __str__(self):
        return '<CPlane(%i) at %s>' % (len(self.items), id(self))
    
    def __repr__(self):
        return 'CoordinatePlane(%r)' % (self.items)
    
    def moveItem(self, item, vector):
        """ Move an object by a relative vector. """
        delta = Point.fromVector(vector.dir.angle, vector.dist)
        if item not in self.items:
            raise ValueError, "Item does not yet exist in world: %s" % item
        
        oldLoc = self.items[item]
        newLoc = oldLoc + delta
        if newLoc.loc in self.locations:
            collidee = self.locations[newLoc.loc]
            if collidee is not item:
                raise CollisionError(item, newLoc,
                                     collidee, self.items[collidee])
        self.items[item] = newLoc
        del self.locations[oldLoc.loc]
        self.locations[newLoc.loc] = item
        
    def moveItemTo(self, item, point):
        """ Move an item to a specific location. """
        if point.loc in self.locations:
            collidee = self.locations[point.loc]
            raise CollisionError(item, point,
                                 collidee, self.items[collidee])
        elif item in self.items:
            oldLoc = self.items[item]
            del self.locations[oldLoc.loc]
            
        self.items[item] = point
        self.locations[point.loc] = item
        
    def append(self, item, point):
        """ Add a non-pre-extant item. """
        if item in self.items:
            raise ValueError, "Item already exists in world: %s" % item
        self.moveItemTo(item, point)

    def getCoord(self, item):
        """ Return the location of a specific object. """
        return self.items[item]

    def getNearestItem(self, source, targetType):
        least = None
        nearestItem = None
        sourceLoc = self.items[source]
        for item,loc in self.items.items():
            if type(item) is not targetType: continue
            dist = sourceLoc.dist(loc)
            if least is None or dist < least:
                nearestItem = item
                least = dist
        return nearestItem
    
    def getItems(self, x, y, w, h):
        x2 = x + w
        y2 = y + h
        items = [ (loc,item) for item,loc in self.items.items()
                             if loc.x >= x and loc.x <= x2 and
                                loc.y >= y and loc.y <= y2 ]
        return items

    def checkCollision(self, location):
        if location.loc in self.locations:
            collidee = self.locations[location.loc]
            return (collidee, self.items[collidee])
        return None

class World(CoordinatePlane):
    """
    Maintains the locations of game objects, and the landscape. Core
    functionality moved for reusability.
    """
    # Location by object -> x,y are floats
    # items[<item>] => Point(x,y)
    items = {}
    # Object by location -> x,y are integers
    # locations[(x,y)] => item
    locations = {}
    tiles = {}
    game = None
    dirty = False

    def __init__(self, itemLocs={}, game=None):
        for item, coord in itemLocs:
            self.items[item] = coord
            self.locations[coord] = item
        self.game = game

    def __str__(self):
        return '<World(%i) at %s>' % (len(self.items), id(self))
    
    def __repr__(self):
        return 'World(%r)' % (self.items)

    # Landscape.
    def getTile(self, x, y):
        loc = (int(x), int(y))
        if loc not in self.tiles:
            return "water"
        return self.tiles[loc]
    
    def setTile(self, x, y, tileName):
        loc = (int(x), int(y))
        self.tiles[loc] = tileName
        self.dirty = True
        return True


    # Loading and Saving via Resources
    def load(self, res, mapName):
        self.tiles = res.loadMap(mapName)
        self.dirty = False
    
    def save(self, res, mapName):
        if self.dirty:
            tiles = self.tiles.items()
            tiles.sort()
            res.saveMap(mapName, tiles)
            self.dirty = False


